import { Asset, assetManager, AssetManager, js, SpriteFrame, Texture2D } from "cc";
import { Platform } from "../base/BasePlatform";
import { ArgsParseUtils, LoadResArgs, LoadSpriteFrameArgs } from "../utils/ArgsParseUtils";
import { TimeUtils } from "../utils/TimeUtils";
import { LogMgr } from "./LogMgr";
import { PlatformManager } from "./PlatformManager";
import { ResCacheMgr } from "./ResCacheMgr";
export type CompleteCallback<T = any> = (data?: T | null) => void;
export type preloadCompleteCallback<T = any> = (err: Error | null, data?: AssetManager.RequestItem[] | null) => void;

/**
 * 资源缓存类
 */
export class ResCache {
    cachekey: string;  //key名字
    createTime: number;  //创建事件
    releaseTime: number;   //释放时间
    cacheTime: number;   //缓存有效时间
    type: string;        //类型
    asset: Asset;     //资源
    lifeTime: number     //生命有效时间（秒）
    defCount: number;    //引用数量需要减多少
    canRelease: boolean;    //是否可以释放
    constructor(bundle: string, path: string, dateTime: number, time: number, type: typeof Asset, asset?: Asset) {
        this.releaseTime = dateTime;
        this.cacheTime = time;
        this.asset = asset;
        this.type = js.getClassName(type)
        this.cachekey = bundle + "/" + path
        this.defCount = 0;
        this.lifeTime = 3
        this.createTime = TimeUtils.GetTimeBySecond()
        this.canRelease = true
    }
    setLifeTime(val: number) {
        this.lifeTime = val;
    }
}

/*
 * 资源缓存Key类
 */
export class ResCacheKey {
    cachekey: string;  //key名字
    type: string;        //类型
    defCount: number = 0;
    constructor(bundle: string, path: string, type: typeof Asset) {
        this.type = js.getClassName(type)
        this.cachekey = bundle + "/" + path
        this.defCount = 1;
    }
}


/**
 * bundle缓存类
 */
export class BundleCache {
    bundleName: string;  //key名字
    dateTime: number;    //当前时间
    cacheTime: number;   //有效时间
    bundle: AssetManager.Bundle;//bundle资源
    constructor(bundleName: string, dateTime: number, cacheTime: number, bundle?: Asset) {
        this.bundleName = bundleName
        this.dateTime = dateTime;
        this.cacheTime = cacheTime;
        //   this.bundle = bundle;
    }
}
export class ResourceMgr {

    private static instance: ResourceMgr = null;
    protected constructor() { }
    public static getInstance(): ResourceMgr {
        if (this.instance == null) {
            this.instance = new ResourceMgr();
        }
        return this.instance;
    }


    /**
     * 获取bundle
     * @param bundlename 
     * @returns 
     */
    public getBundle(bundlename: string): AssetManager.Bundle {
        return assetManager.getBundle(bundlename);
    }
    /**
     * 加载assets bundle
     * @param bundleName bundle的名字
     * @param callback 
     */
    public loadBundle(bundleName: string, callback: Function) {
        let bundle = assetManager.getBundle(bundleName)
        if (bundle == null) {
            assetManager.loadBundle(bundleName, (er: Error, bunlde2: AssetManager.Bundle) => {
                if (er) {
                    LogMgr.getInstance().error(bundleName + "bundle load error :" + er)
                    return;
                }
                LogMgr.getInstance().debug("加载bundle:" + bundleName)
                callback(bunlde2)
            })
        } else {
            callback(bundle)
        }
    }
    /**预加载资源 */
    public preloadFromBundle(bundle: AssetManager.Bundle, path: string[], callback?: preloadCompleteCallback) {
        bundle.preload(path, Asset, (er: Error, resArr: AssetManager.RequestItem[]) => {
            if (callback) {
                callback(er, resArr)
            }
        })
    }
    public preload(bundleName: string, path: string | string[]) {
        let bundle = assetManager.getBundle(bundleName)
        if (bundle) {
            if (path) {
                bundle.preload(path)
            }
        } else {
            assetManager.loadBundle(bundleName, (er: Error, res: AssetManager.Bundle) => {
                if (!er) {
                    if (path) {
                        res.preload(path)
                    }
                }
            })
        }
    }
    /**
     * eg:
     * let bundles: string[] = ["scene", "prefabs"]
     * let pathsMap: Map<number, string[]> = new Map();
     * pathsMap.set(0, ["pic1","pic2"])
     * pathsMap.set(1, ["UITop", "UIChapter"])
     * 加载bundle并且加载资源
     * @param bundleNames 需要加载的bundle数组  
     * @param preloadPathMap 对应的资源路径  
     */
    public relLoadBundle(bundleNames: string[], preloadPathMap?: Map<number, string[]>) {
        for (let i = 0; i < bundleNames.length; i++) {
            let bundleName = bundleNames[i]
            this.loadBundle(bundleName, (bundle: AssetManager.Bundle) => {
                if (preloadPathMap) {
                    let paths = preloadPathMap.get(i)
                    bundle.load(paths)
                }
            })
        }
    }
    /**
     * 从resources文件中加载资源
     * @param path 资源路径
     * @param callback 回调
     */
    public loadFromRes(path: string, type: typeof Asset, callback: CompleteCallback) {
        this.loadRes("resources", path, type, callback)
    }
    public loadDirFrom(dir: string, callback: CompleteCallback) {
        this.loadDir("resources", dir, callback)
    }
    /**
     * 加载图片
     * @param bundleName 
     * @param path 
     * @param callback 
     */
    public loadSpriteframe(bundleName: string, path: string, callback: CompleteCallback);
    public loadSpriteframe(bundleName: string, path: string, callback: CompleteCallback, saveKey: string);
    public loadSpriteframe(bundleName: string, path: string, callback: CompleteCallback, autoRelese: boolean, cacheTme: number);
    public loadSpriteframe(bundleName: string, path: string, callback: CompleteCallback, autoRelese: boolean, cacheTme: number, saveKey: string);
    public loadSpriteframe() {
        let args: LoadSpriteFrameArgs = ArgsParseUtils._makeloadSpriteFrameResrgs.apply(this, arguments);
        let bundleName: string = args.bundle
        let path: string = args.path
        if (!path.endsWith("spriteFrame")) {
            path += "/spriteFrame"
        }
        let callback: CompleteCallback = args.callback;
        let saveKey = args.saveKey;
        let dateTime = TimeUtils.GetTimeBySecond() + 60 * 60 * 24;
        let resCache = ResCacheMgr.getInstance().getAssets(bundleName, path, SpriteFrame);
        if (resCache && resCache.asset) {
            resCache.asset.addRef();
            resCache.releaseTime = dateTime;
            callback(resCache.asset);
            return
        }
        let bundle = assetManager.getBundle(bundleName)
        let assetsCache = new ResCache(bundleName, path, dateTime, args.cacheTme, SpriteFrame);
       
        if (bundle == null) {
            ResourceMgr.getInstance().loadBundle(bundleName, (bundle2: AssetManager.Bundle) => {
                bundle2.load(path, SpriteFrame, (er: Error, res: SpriteFrame) => {
                    if (er) {
                        LogMgr.getInstance().error("loadres error path:" + path + "  error:", er.message)
                        return;
                    }
                    res.addRef();
                    assetsCache.asset = res;
                    ResCacheMgr.getInstance().addAssets(assetsCache, args.autoRelese)
                    if (saveKey) {
                        ResCacheMgr.getInstance().addAssetKeyToMap(saveKey, bundleName, path, SpriteFrame)
                    }
                    if (callback) {
                        callback(res);
                    }

                })
            })
        } else {
            bundle.load(path, SpriteFrame, (er: Error, res: SpriteFrame) => {
                if (er) {
                    LogMgr.getInstance().error("loadres error path:" + path + "  error:" + er)
                    return;
                }
                res.addRef();
                assetsCache.asset = res;
                ResCacheMgr.getInstance().addAssets(assetsCache, args.autoRelese)
                if (saveKey) {
                    ResCacheMgr.getInstance().addAssetKeyToMap(saveKey, bundleName, path, SpriteFrame)
                }
                if (callback) {
                    callback(res);
                }

            })
        }
    }


    /**
     * 加载远程图片
     * @param url   图片URL 小游戏需要在后台添加URL域名白名单
     * @param callback  
     * @param autoRelese 关闭界面后是否自动释放 默认false
     * @param cacheTme   缓存时间
     * @returns 
     */
    public loadRemoteSpriteFrame(url: string, callback: CompleteCallback, autoRelese: boolean = false, cacheTme: number = 10) {
        let dateTime = TimeUtils.GetTimeBySecond() + 60 * 60 * 24
        let resCache = ResCacheMgr.getInstance().getAssets('', url, Texture2D)
        if (resCache && resCache.asset) {
            resCache.asset.addRef()
            callback(resCache.asset)
            resCache.releaseTime = dateTime;
            return
        }
        let assetsCache = new ResCache("", url, dateTime, cacheTme, Texture2D);
        if (url.startsWith("http")) {
            // if (!path.endsWith(".png") || !path.endsWith(".jpg") || !path.endsWith(".jpeg")) {
            //     path += ".png"
            // }
            if (PlatformManager.currentPlatform == Platform.BYTEDANCE) {
                assetManager.loadRemote(url, { ext: ".head" }, (eror: Error, res: Texture2D) => {
                    if (eror) {
                        return;
                    }
                    res.addRef()
                    assetsCache.asset = res;
                    ResCacheMgr.getInstance().addAssets(assetsCache, autoRelese)
                    let sp = new SpriteFrame()
                    sp.texture = res;
                    callback(sp)
                })
            } else {
                assetManager.loadRemote(url, (eror: Error, res: Texture2D) => {
                    if (eror) {
                        return;
                    }
                    res.addRef()
                    assetsCache.asset = res;
                    ResCacheMgr.getInstance().addAssets(assetsCache, autoRelese)
                    let sp = new SpriteFrame()
                    sp.texture = res;
                    callback(sp)
                })
            }
        }

    }



    /**
     * 加载资源
     */
    public loadRes(bundleName: string, path: string, type: typeof Asset, callback: CompleteCallback): void;
    public loadRes(bundleName: string, path: string, type: typeof Asset, callback: CompleteCallback, saveKey: string): void;
    public loadRes(bundleName: string, path: string, type: typeof Asset, callback: CompleteCallback, autoRelese: boolean, cacheTme: number): void;
    public loadRes(bundleName: string, path: string, type: typeof Asset, callback: CompleteCallback, autoRelese: boolean, cacheTme: number, saveKey: string): void;
    public loadRes() {
        let args: LoadResArgs = ArgsParseUtils._makeLoadResArgs.apply(this, arguments);
        let bundleName: string = args.bundle
        let path: string = args.path
        let type: typeof Asset = args.type
        let callback: CompleteCallback = args.callback
        let saveKey = args.saveKey;
        if (js.getClassName(type) == 'Asset') {
            console.error('请传入具体资源类型，不能是基类asset,路径:', bundleName + '/' + path);
            return;
        }
        //判断缓存中是否已存在该资源
        let dateTime = TimeUtils.GetTimeBySecond() + 60 * 60 * 24;
        let resCache = ResCacheMgr.getInstance().getAssets(bundleName, path, type)
        if (resCache && resCache.asset) {
            resCache.asset.addRef()
            resCache.releaseTime = dateTime;
            callback(resCache.asset)
            return
        }
        //从bundle中加载资源
        let bundle = assetManager.getBundle(bundleName)
        let assetsCache = new ResCache(bundleName, path, dateTime, args.cacheTme, type);
        if (bundle == null) {
            ResourceMgr.getInstance().loadBundle(bundleName, (bundle2: AssetManager.Bundle) => {
                bundle2.load(path, type, (er: Error, res: Asset) => {
                    if (er) {
                        LogMgr.getInstance().error("loadres error path:" + path + "  error:", er.message)
                        return;
                    }
                    res.addRef();
                    assetsCache.asset = res;
                    ResCacheMgr.getInstance().addAssets(assetsCache, args.autoRelese)
                    if (saveKey) {
                        ResCacheMgr.getInstance().addAssetKeyToMap(saveKey, bundleName, path, type)
                    }
                    if (callback) {
                        callback(res);
                    }
                })
            })
        } else {
            bundle.load(path, type, (er: Error, res: Asset) => {
                if (er) {
                    LogMgr.getInstance().error("loadres error path:" + path + "  error:" + er)
                    return;
                }
                res.addRef();
                assetsCache.asset = res;
                ResCacheMgr.getInstance().addAssets(assetsCache, args.autoRelese)
                if (saveKey) {
                    ResCacheMgr.getInstance().addAssetKeyToMap(saveKey, bundleName, path, type)
                }
                if (callback) {
                    callback(res);
                }
            })
        }
    }


    /**
     * 加载目录下所有资源
     * @param bundleName 
     * @param path 
     * @param callback 
     */
    public loadDir(bundleName: string, path: string, callback: CompleteCallback) {
        let bundle = assetManager.getBundle(bundleName)
        if (bundle == null) {
            assetManager.loadBundle(bundleName, (er: Error, bundle2: AssetManager.Bundle) => {
                bundle2.loadDir(path, Asset, (er: Error, res: Asset[]) => {
                    if (er) {
                        LogMgr.getInstance().error("loadres error path:" + path + "  error:" + er)
                        return;
                    }
                    if (callback) {
                        callback(res);
                    }
                })
            })
        } else {
            bundle.loadDir(path, Asset, (er: Error, res: Asset[]) => {
                if (er) {
                    LogMgr.getInstance().error("loadres error path:" + path + "  error:" + er)
                    return;
                }
                if (callback) {
                    callback(res);
                }
            })
        }

    }

    /**从bundle释放一个资源 */
    public releaseByBundle(bundle: AssetManager.Bundle, path: string) {
        bundle.release(path)

    }
    /**根据bundle名字释放一个资源 */
    public releaseByBname(bundleName: string, path: string) {
        let bundle = assetManager.getBundle(bundleName)
        if (bundle) {
            bundle.release(path)
        }
    }

    public releaseBundle(bundleName: string) {
        let bundle = assetManager.getBundle(bundleName)
        if (bundle) {
            assetManager.removeBundle(bundle)
        }

    }

}